package com.zareal.ymcc.service.impl;

import com.alibaba.fastjson.JSON;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.Config;
import com.alipay.easysdk.kernel.util.ResponseChecker;
import com.alipay.easysdk.payment.common.models.AlipayTradeRefundResponse;
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
import com.zareal.ymcc.domain.AlipayInfo;
import com.zareal.ymcc.domain.PayFlow;
import com.zareal.ymcc.domain.PayOrder;
import com.zareal.ymcc.dto.AlipayNotifyDto;
import com.zareal.ymcc.dto.PayApplyDto;
import com.zareal.ymcc.dto.PayNotifyLocalTxParamDto;
import com.zareal.ymcc.enums.GlobalErrorCode;
import com.zareal.ymcc.exception.GlobalCustomException;
import com.zareal.ymcc.result.JSONResult;
import com.zareal.ymcc.service.IAlipayInfoService;
import com.zareal.ymcc.service.IPayOrderService;
import com.zareal.ymcc.service.IPayService;
import com.zareal.ymcc.util.AssertUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

/**
 * @description:
 * @author: Bug修复机
 * @date: 2022年10月17日 ： 14:38
 * @version: ver1.0
 * @email tangli@itsource.cn
 */
@Service
@Slf4j
public class PayServiceImpl implements IPayService {

    @Autowired
    private IPayOrderService payOrderService;

    @Autowired
    private IAlipayInfoService alipayInfoService;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Override
    public JSONResult checkPayOrder(String orderNo) {
        return payOrderService.selectByOrderNo(orderNo) == null?JSONResult.error():JSONResult.success();
    }

    @Override
    public String apply(PayApplyDto payDto) {
        log.info("发起支付，订单号为：{}", payDto.getOrderNo());
        // 1.参数校验使用JSR303完成
        // 2.业务校验
        PayOrder payOrder = payOrderService.selectByOrderNo(payDto.getOrderNo());
        AssertUtil.isNotNull(payOrder, GlobalErrorCode.PAY_ORDER_IS_NULL);
        boolean isOk = PayOrder.WAIT_PAY_STATUS == payOrder.getPayStatus();
        AssertUtil.isTrue(isOk, GlobalErrorCode.PAY_STATUS_IS_ERROR);

        // 3.根据支付方式调用对应的支付API
        if (payOrder.getPayType() == 1){
            log.info("调用支付宝进行支付开始!");
            return aliPay(payOrder, payDto.getCallUrl());
        }else if (payOrder.getPayType() == 2){
            // 可以用策略模式进行if改造
        }

        return null;
    }



    private String aliPay(PayOrder payOrder, String callUrl) {
        AlipayInfo alipayInfo = alipayInfoService.selectOne(null);
        // 1. 设置参数（全局只需设置一次）
        Factory.setOptions(getOptions(alipayInfo));
        try {
            // 2. 发起API调用,网页支付easy版
            AlipayTradePagePayResponse response = Factory.Payment.Page().pay(payOrder.getSubject(), payOrder.getOrderNo(), payOrder.getAmount().toString(), callUrl);
            // 3. 处理响应或异常
            if (ResponseChecker.success(response)) {
                log.info("调用支付宝成功!");
                return response.getBody();
            } else {
                // 修改订单状态为失败....
            }
        } catch (Exception e) {
            log.info("调用遭遇异常，原因：" + e.getMessage());
            // 修改订单状态为失败....
            throw new GlobalCustomException(GlobalErrorCode.ERROR);
        }
        return null;
    }

        /**
         * 异步回调业务处理
         * @param alipayNotifyDto
         * @return
         */
        @Override
        public String alipayNotify(AlipayNotifyDto alipayNotifyDto) {
            try {
                Map map = JSON.parseObject(JSON.toJSONString(alipayNotifyDto), Map.class);
                // 我们直接把所有参数给支付宝进行验签，支付宝会响应验签结果
                Boolean isSign = Factory.Payment.Common().verifyNotify(map);
                if (!isSign){
                    return "success";
                }

                // 1.判断支付状态必须为成功，我们才进行下一步
                if (!alipayNotifyDto.getTrade_status().equals(AlipayNotifyDto.TRADE_SUCCESS)){
                    return "success"; // 如果状态不对，那么我们返回失败，让支付宝后续继续通知我
                }
                // 2.通过订单编号查询支付单，看是否存在
                PayOrder payOrder = payOrderService.selectByOrderNo(alipayNotifyDto.getOut_trade_no());
                if (payOrder == null){
                    return "success";
                }

                // 3.判断金额是否正确
                if (payOrder.getAmount().compareTo(new BigDecimal(alipayNotifyDto.getTotal_amount())) != 0){
                    return "success";
                }

                // 4.判断支付单状态为取消时，进行退款
                if (PayOrder.CANCEL_PAY_STATUS.equals(payOrder.getPayStatus()) || PayOrder.FAIL_PAY_STATUS.equals(payOrder.getPayStatus())){
                    // 退款
                    try {
                        AlipayTradeRefundResponse result = Factory.Payment.Common().refund(alipayNotifyDto.getOut_trade_no(), alipayNotifyDto.getTotal_amount());
                        if (!result.getCode().equals("10000")){
                            // 业务处理，重试三次，如果都不成功那么记录失败日志表，并且给你运维、管理员发钉钉、邮件、站内信等消息，让他们速度处理
                        }else {
                            // 退款成功，单独处理支付单和订单流水
                            return "success";
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        // 业务处理，重试三次，如果都不成功那么记录失败日志表，并且给你运维、管理员发钉钉、邮件、站内信等消息，让他们速度处理
                    }
                }

                // 5.如果我们的状态是已经完成了，那么直接响应成功
                if (PayOrder.SUCCESS_PAY_STATUS.equals(payOrder.getPayStatus())){
                    return "success";
                }

                // 6.准备事务消息本地事务的参数
                PayNotifyLocalTxParamDto payNotifyLocalTxParamDto = initPayNotifyLocalTxparam(payOrder, alipayNotifyDto);
                // 6.1.用作arg
                String arg = JSON.toJSONString(payNotifyLocalTxParamDto);
                // 6.2.封装message
                String extParams = payOrder.getExtParams();
                Message<String> message = MessageBuilder.withPayload(extParams).build();

                // 7.发送mq事务消息 object：是给监听器执行本地事务的，message是消费方进行消费的 1.订单编号、支付总金额、支付方式、订单标题、
                TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction(
                        "tx-pay-notify-group",
                        "tx-pay-notify-topic:tx-pay-notify-tag",
                        message,
                        arg);
                // 判断发送消息是否失败
                boolean sendOk = result.getSendStatus() == SendStatus.SEND_OK;
                if (!sendOk){
                    // 业务处理，让支付宝再发，如果都不成功那么记录失败日志表，并且给你运维、管理员发钉钉、邮件、站内信等消息，让他们速度处理
                    return "fail";
                }
            } catch (Exception e) {
                e.printStackTrace();
                // 业务处理，让支付宝继续发8次，如果都不成功那么记录失败日志表，并且给你运维、管理员发钉钉、邮件、站内信等消息，让他们速度处理
                return "fail";
            }
            return "success";
        }

        /**
         * 初始化异步通知的本地事务消息参数对象
         * @param payOrder
         * @param alipayNotifyDto
         * @return
         */
        private PayNotifyLocalTxParamDto initPayNotifyLocalTxparam(PayOrder payOrder, AlipayNotifyDto alipayNotifyDto) {
            try {
                payOrder.setPayStatus(PayOrder.SUCCESS_PAY_STATUS);
                payOrder.setUpdateTime(new Date());
                PayFlow payFlow = new PayFlow();
                payFlow.setCode(alipayNotifyDto.getCode());
                payFlow.setMsg(alipayNotifyDto.getMsg());
                SimpleDateFormat simpleFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                Date parse = simpleFormatter.parse(alipayNotifyDto.getNotify_time());
                payFlow.setNotifyTime(parse);
                payFlow.setOutTradeNo(alipayNotifyDto.getOut_trade_no());
                payFlow.setPassbackParams(alipayNotifyDto.getPassback_params());
                payFlow.setPaySuccess(true);
                payFlow.setSubject(alipayNotifyDto.getSubject());
                payFlow.setResultDesc(alipayNotifyDto.getBody());
                payFlow.setTotalAmount(new BigDecimal(alipayNotifyDto.getTotal_amount()));
                payFlow.setTradeStatus(alipayNotifyDto.getTrade_status());
                return new PayNotifyLocalTxParamDto(payOrder, payFlow);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            return null;
        }

    /**
     * 设置支付宝基础参数
     * @return
     */
    private static Config getOptions(AlipayInfo alipayInfo) {
        Config config = new Config();
        config.protocol = alipayInfo.getProtocol();
        config.gatewayHost = alipayInfo.getGatewayHost();
        config.signType = alipayInfo.getSignType();
        config.appId = alipayInfo.getAppId();
        // 为避免私钥随源码泄露，推荐从文件中读取私钥字符串而不是写入源码中
        config.merchantPrivateKey = alipayInfo.getMerchantPrivateKey();
        //注：如果采用非证书模式，则无需赋值上面的三个证书路径，改为赋值如下的支付宝公钥字符串即可
        config.alipayPublicKey = alipayInfo.getAlipayPublicKey();
        //可设置异步通知接收服务地址（可选）
        config.notifyUrl = alipayInfo.getNotifyUrl();
        return config;
    }

}
